/*
    RExLib project, file rtcpsocket.cpp
    Copyright (C) 2012  Sarvaritdinov Ravil ra9oaj@gmail.com

    This library is free software; you can redistribute it and/or
    modify it under the terms of the GNU Lesser General Public
    License as published by the Free Software Foundation; either
    version 2.1 of the License, or (at your option) any later version.

    This library is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
    Lesser General Public License for more details.

    You should have received a copy of the GNU Lesser General Public
    License along with this library; if not, write to the Free Software
    Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
*/


#include "rtcpsocket.h"

namespace rex
{

RTcpSocket::RTcpSocket()
{
    _addr = 0;
    _errno = RE_NONE;
    _state = RS_UNCONNECT;
    _port = _sfd = 0;
    _host = 0;
    _num_addr = 0;
}

RTcpSocket::RTcpSocket ( const RTcpSocket& other )
{
    _addr = 0;
    _errno = RE_NONE;
    _state = RS_UNCONNECT;
    _port = _sfd = 0;
    _host = 0;
    _num_addr = 0;

    setSocketFd ( other.socketFd() );
}

RTcpSocket::~RTcpSocket()
{
    if ( _sfd )
        closeSocket();
    clear();
}

bool RTcpSocket::setSocketLayer ( RAbstractSocket::AddressFamily _sl )
{
    if ( _state != RS_UNCONNECT && _state != RS_ERROR && _state != RS_ERROR )
        return false;

    if ( _sfd )
        closeSocket();

    if ( _addr )
    {
        free ( _addr );
        _addr = 0;
    }


    if ( _sl == RAbstractSocket::RS_INET )
    {
        struct sockaddr_in *_in4 = ( struct sockaddr_in * ) malloc ( sizeof ( struct sockaddr_in ) );
        _in4->sin_family = AF_INET;
        _addr = ( struct sockaddr * ) _in4;
    }
    else if ( _sl == RAbstractSocket::RS_INET6 )
    {
        struct sockaddr_in6 *_in6 = ( struct sockaddr_in6 * ) malloc ( sizeof ( struct sockaddr_in6 ) );
        _in6->sin6_family = AF_INET;
        _addr = ( struct sockaddr * ) _in6;
    }
    else
    {
        _state = RS_ERROR;
        _errno = RE_ADDRESSFAMILY;
        return false;
    }

    _sfd = socket ( _addr->sa_family,SOCK_STREAM,0 );
    if ( !_sfd )
    {
        _errno = RE_CREATESOCKET;
        _state = RS_ERROR;
        _sfd = 0;
        return false;
    }
    return true;
}

uint16_t RTcpSocket::port() const
{
    if ( !_port )
    {
        if ( _state == RS_CONNECTED || _state == RS_CONNECTING )
        {
            if ( _addr->sa_family == AF_INET )
                return htonl ( ( ( struct sockaddr_in * ) _addr )->sin_port );
            else return htonl ( ( ( struct sockaddr_in6 * ) _addr )->sin6_port );
        }
    }
    return _port;
}

const char* RTcpSocket::host ( AddressFormat format ) const
{
    if ( format == RS_NUMERIC )
        return _num_addr;

    return _host;
}

void RTcpSocket::setHostName ( const char *host )
{
    if ( _host )
        free ( _host );
    _host = ( char* ) malloc ( strlen ( host ) + 1 );
    _host = strcpy ( _host,host );

    size_t slen = INET_ADDRSTRLEN + 1;
    if ( _num_addr )
        free ( _num_addr );


    if ( _addr->sa_family == AF_INET6 )
    {
        slen = INET6_ADDRSTRLEN + 1;
        _num_addr = ( char* ) malloc ( slen );
        if ( !inet_ntop ( AF_INET6, & ( ( ( struct sockaddr_in6 * ) _addr )->sin6_addr ), _num_addr, slen ) )
        {
            free ( _num_addr );
            _num_addr = 0;
        }
    }
    else
    {
        _num_addr = ( char* ) malloc ( slen );
        if ( !inet_ntop ( AF_INET, & ( ( ( struct sockaddr_in * ) _addr )->sin_addr ), _num_addr, slen ) )
        {
            free ( _num_addr );
            _num_addr = 0;
        }
    }
}

int RTcpSocket::socketFd() const
{
    return _sfd;
}

bool RTcpSocket::setSocketFd ( const int& sockfd )
{
    if ( _state == RS_UNCONNECT || _state == RS_DISCONNECTED || _state == RS_ERROR )
    {
        struct sockaddr addr;
        bzero ( &addr,sizeof ( addr ) );
        socklen_t len = sizeof ( addr );

        getsockname ( sockfd,&addr,&len );

        if ( addr.sa_family == AF_INET || addr.sa_family == AF_INET6 )
        {
            if ( _addr )
                free ( _addr );

            _addr = ( struct sockaddr * ) malloc ( sizeof ( struct sockaddr_in ) );
            _addr = ( struct sockaddr * ) memcpy ( _addr,&addr,len );
            _sfd = sockfd;

            len = sizeof ( addr );
            bzero ( &addr,len );
            getpeername ( sockfd,&addr,&len );

            char host[NI_MAXHOST];
            getnameinfo ( &addr,len,host,NI_MAXHOST,0,0,0 );
            size_t slen = strlen ( host );
            if ( slen )
            {
                _state = RS_CONNECTED;
                _errno = RE_NONE;
                setHostName ( host );
                _port = addr.sa_family == AF_INET ? htons ( ( ( struct sockaddr_in * ) &addr )->sin_port ) : htons ( ( ( struct sockaddr_in6 * ) &addr )->sin6_port );
            }
            return true;
        }
        else
        {
            _state = RS_ERROR;
            _errno = RE_ADDRESSFAMILY;
            return false;
        }
    }

    return false;
}

bool RTcpSocket::connectToHost ( const char* host, const uint16_t& prt )
{
    if ( _state != RS_DISCONNECTED && _state != RS_UNCONNECT && _state != RS_ERROR )
        return false;

    if ( !_addr )
    {
        if ( !setSocketLayer ( RS_INET ) )
            return false;
    }

    struct hostent *hst = gethostbyname ( host );

    if ( !hst )
    {
        _errno = RE_HOSTNOTFOUND;
        _state = RS_ERROR;
        closeSocket();
        return false;
    }

    if ( !hst->h_length )
    {
        _errno = RE_HOSTNOTFOUND;
        _state = RS_ERROR;
        closeSocket();
        return false;
    }

    for ( int i = 0; i < hst->h_length; ++i )
    {
        switch ( hst->h_addrtype )
        {
        case AF_INET:
            ( ( struct sockaddr_in* ) _addr )->sin_addr.s_addr = * ( uint32_t * ) hst->h_addr_list[i];
            ( ( struct sockaddr_in* ) _addr )->sin_port = htons ( prt );
            break;

        case AF_INET6:
            ( ( struct sockaddr_in6* ) _addr )->sin6_addr = * ( struct in6_addr * ) hst->h_addr_list[i];
            ( ( struct sockaddr_in6* ) _addr )->sin6_port = htons ( prt );
            break;

        default:
            _errno = RE_ADDRESS;
            _state = RS_ERROR;
            continue;
        }


        socklen_t slen = ( _addr->sa_family == AF_INET ? sizeof ( struct sockaddr_in ) : sizeof ( struct sockaddr_in6 ) );
        int res = connect ( _sfd,_addr,slen );
        if ( res != 0 )
        {
            if ( errno == EINPROGRESS )
            {
                setHostName ( host );
                _port = prt;
                _errno = RE_NONE;
                _state = RS_CONNECTING;
                return true;
            }
            _errno = RE_CONNECTION;
            _state = RS_ERROR;
            continue;
        }

        setHostName ( host );
        _port = prt;
        _errno = RE_NONE;
        _state = RS_CONNECTED;
        return true;
    }

    closeSocket();
    return false;
}

void RTcpSocket::closeSocket()
{
    closeSockFd();
    _state = RS_UNCONNECT;
    _errno = RE_NONE;
}

void RTcpSocket::closeSockFd()
{
    if ( _sfd )
    {
        close ( _sfd );
        _sfd = 0;
    }
}

void RTcpSocket::setNonBlock ( bool flag )
{
    int flags = fcntl ( _sfd,F_GETFL,0 );
    if ( flag && ! ( flags & O_NONBLOCK ) )
        fcntl ( _sfd,F_SETFL,flags | O_NONBLOCK );
    else if ( !flag && ( flags & O_NONBLOCK ) )
        fcntl ( _sfd,F_SETFL,flags ^ O_NONBLOCK );
}

int RTcpSocket::status()
{
    return _state;
}

int RTcpSocket::error() const
{
    return _errno;
}

size_t RTcpSocket::write ( const char* buf, int len )
{
    if ( len <= 0 || !_sfd ) return 0;

    ssize_t n = send ( _sfd,buf,len,MSG_NOSIGNAL );
    if ( n < 0 )
    {
        _state = RS_ERROR;
        _errno = RE_WRITE;
        closeSocket();
    }

    return ( size_t ) n;
}

char* RTcpSocket::read ( int len )
{
    if ( len <=0 || !_sfd ) return 0;

    char *buf = new char[len];
    ssize_t n = recv ( _sfd,buf,len,MSG_NOSIGNAL );
    if ( n < 0 )
        return 0;
    return buf;
}

size_t RTcpSocket::read ( char* buf, int len )
{
    if ( len <=0 || !_sfd ) return 0;

    ssize_t n = recv ( _sfd,buf,len,MSG_NOSIGNAL );
    if ( n < 0 )
    {
        _state = RS_ERROR;
        _errno = RE_READ;
        return 0;
    }
    return ( size_t ) n;
}

size_t RTcpSocket::bytesAvailable() const
{
    if ( !_sfd ) return 0;

    size_t n = 0;
    if ( ioctl ( _sfd,FIONREAD, &n ) < 0 )
        return 0;
    return n;
}

void RTcpSocket::clear()
{
    if ( _state == RS_CONNECTED || _state == RS_CONNECTING || _state == RS_HOSTLOOKUP )
        closeSocket();

    if ( _addr )
        free ( _addr );
    if ( _host )
        free ( _host );
    if ( _num_addr )
        free ( _num_addr );
    _addr = 0;
    _errno = RE_NONE;
    _state = RS_UNCONNECT;
    _port = _sfd = 0;
    _host = 0;
}

}
